package proguard.retrace;

import proguard.obfuscate.MappingReaderExt;

import java.io.*;

/**
 * 修改了{@link ReTrace}的部分方法的可见性范围
 * 增加换行符
 * 并提供了更多方便的方法
 */
public class ReTraceExt extends ReTrace {

    private final FramePattern pattern1;
    private final FramePattern pattern2;

    public ReTraceExt() {
        this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, false, false);
    }

    /**
     * @param verbose true--反混淆时输出更多信息，比如方法的入参和返回值，如果找不到则显示null
     */
    public ReTraceExt(boolean verbose) {
        this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, false, verbose);
    }

    /**
     *
     * @param allClassNames  true--反混淆那些不匹配正则表达式的类
     * @param verbose true--反混淆时输出更多信息，比如方法的入参和返回值，如果找不到则显示null
     */
    public ReTraceExt(boolean allClassNames, boolean verbose) {
        this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, allClassNames, verbose);
    }

    public ReTraceExt(String regularExpression, String regularExpression2, boolean allClassNames, boolean verbose) {
        super(regularExpression, regularExpression2, allClassNames, verbose, null);
        pattern1 = new FramePattern(regularExpression, verbose);
        pattern2 = new FramePattern(regularExpression2, verbose);
    }

    public String retrace(File mappingFile, File stackTraceFile) throws IOException {
        return retrace(mappingFile, new FileInputStream(stackTraceFile));
    }

    public String retrace(File mappingFile, InputStream stackTraceInputStream) throws IOException {
        LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
        return retrace(getFrameRemapper(mappingFile), reader);
    }

    public String retrace(File mappingFile, String stackTraceString) throws IOException {
        return retrace(getFrameRemapper(mappingFile), stackTraceString);
    }

    public String retrace(InputStream mappingInputStream, File stackTraceFile) throws IOException {
        return retrace(mappingInputStream, new FileInputStream(stackTraceFile));
    }

    public String retrace(InputStream mappingInputStream, InputStream stackTraceInputStream) throws IOException {
        LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
        return retrace(getFrameRemapper(mappingInputStream), reader);
    }

    public String retrace(InputStream mappingInputStream, String stackTraceString) throws IOException {
        return retrace(getFrameRemapper(mappingInputStream), stackTraceString);
    }

    public String retrace(String mappingString, File stackTraceFile) throws IOException {
        return retrace(mappingString, new FileInputStream(stackTraceFile));
    }

    public String retrace(String mappingString, InputStream stackTraceInputStream) throws IOException {
        LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
        return retrace(getFrameRemapper(mappingString), reader);
    }

    public String retrace(String mappingString, String stackTraceString) {
        return retrace(getFrameRemapper(mappingString), stackTraceString);
    }

    /**
     * 单行文本retrace
     *
     * @param obfuscatedLine 混淆过的单行文本
     * @param mapper
     * @return
     */
    private String retraceLine(FrameRemapper mapper, String obfuscatedLine) {
        if (obfuscatedLine == null || obfuscatedLine.trim().isEmpty()) {
            return obfuscatedLine;
        }
        // Try to match it against the regular expression.
        FrameInfo obfuscatedFrame1 = pattern1.parse(obfuscatedLine);
        FrameInfo obfuscatedFrame2 = pattern2.parse(obfuscatedLine);

        String deobf = handle(obfuscatedFrame1, mapper, pattern1, obfuscatedLine);
        // DIRTY FIX:
        // I have to execute it two times because recent Java stacktraces may have multiple fields/methods in the same line.
        // For example: java.lang.NullPointerException: Cannot invoke "com.example.Foo.bar.foo(int)" because the return value of "com.example.Foo.bar.foo2()" is null
        deobf = handle(obfuscatedFrame2, mapper, pattern2, deobf);
        return deobf;
    }

    /**
     * De-obfuscates a given stack trace
     *
     * @param stackTraceReader a reader for the obfuscated stack trace
     * @return 翻译后的字符串
     * @throws IOException
     */
    public String retrace(FrameRemapper mapper, LineNumberReader stackTraceReader) throws IOException {
        StringBuilder result = new StringBuilder();
        // Read and process the lines of the stack trace.
        while (true) {
            // Read a line.
            String obfuscatedLine = stackTraceReader.readLine();
            if (obfuscatedLine == null) {
                break;
            }
            result.append(retraceLine(mapper, obfuscatedLine)).append("\n");
        }
        if (stackTraceReader != null) {
            stackTraceReader.close();
        }
        return result.toString();
    }

    private String retrace(FrameRemapper mapper, String stackTraceString) {
        StringBuilder result = new StringBuilder();
        String[] stackTraceLines = stackTraceString.split("\\r?\\n|(?<!\\n)\\r");
        for (int i = 0; i < stackTraceLines.length; i++) {
            String obfuscatedLine = stackTraceLines[i];
            result.append(retraceLine(mapper, obfuscatedLine)).append("\n");
        }
        return result.toString();
    }

    private FrameRemapper getFrameRemapper(File mappingFile) throws IOException {
        MappingReaderExt mappingReader = new MappingReaderExt();
        FrameRemapper mapper = new FrameRemapper();
        mappingReader.pump(mappingFile, mapper);
        return mapper;
    }

    private FrameRemapper getFrameRemapper(InputStream mappingInputStream) throws IOException {
        MappingReaderExt mappingReader = new MappingReaderExt();
        FrameRemapper mapper = new FrameRemapper();
        mappingReader.pump(mappingInputStream, mapper);
        return mapper;
    }

    private FrameRemapper getFrameRemapper(String mappingString) {
        MappingReaderExt mappingReader = new MappingReaderExt();
        FrameRemapper mapper = new FrameRemapper();
        mappingReader.pump(mappingString, mapper);
        return mapper;
    }

    private LineNumberReader getLineNumberReader(InputStream stackTraceInputStream) throws UnsupportedEncodingException {
        return new LineNumberReader(
                new InputStreamReader(stackTraceInputStream, "UTF-8"));
    }

}
